<?php
// $Id: nodereference_asmselect.module,v 1.1.2.5 2008/12/04 12:23:16 nath Exp $
// vim: syntax=php

/**
 * @file
 * Integrates the ASM Select JQuery plugin as an additional NodeReference widget.
 *
 */
define('ASM_SELECT_WIDGET_ID', 'asm_select_widget_id');
define('ASM_SELECT_NODE_TYPE', 'asm_select_node_type');
define('ASM_SELECT_FILTER_PATH', 'nodereference_asmselect_options');
define('ASM_SELECT_MODULE_PATH', drupal_get_path('module', 'nodereference_asmselect'));

/**
 * Implementation of hook_menu().
 */
function nodereference_asmselect_menu($may_cache) {
  $items  = array();

  if ($may_cache) {
    $items[] = array(
      'path'               => ASM_SELECT_FILTER_PATH,
      'callback'           => 'nodereference_asmselect_options',
      'type'               => MENU_CALLBACK,
      'access'             => user_access('access content')
    );
  }

  return $items;
}

function nodereference_asmselect_options() { 
  $filters = explode(',', check_plain($_POST['filters']));
  
  $filters_array = array();
  
  foreach ($filters as $key => $filter) {
    $properties = explode('=', $filter);
    $filters_array[$key]['type'] = $properties[0];
    $filters_array[$key]['value'] = $properties[1];
  }
  
  $field_name = $json_array['field_name'];
  
  $field = content_fields(check_plain($_POST['field_name']));
  
  $nodes = _nodereference_asmselect_get_filtered_options($field, $filters_array);
  
  $output = '';
  
  foreach ($nodes as $value => $title) {
    $output .= '<option value="'. $value .'">'. $title .'</option>';
  }
  
  echo $output;
}

function _nodereference_asmselect_add_filters_js($id, $field_name) {
  drupal_add_js(ASM_SELECT_MODULE_PATH .'/nodereference_asmselect.js');
  $js = "$(document).ready(function() {
    NodeReferenceAsmSelect.settings.filterPath = '". base_path() . ASM_SELECT_FILTER_PATH ."';

    NodeReferenceAsmSelect.fn.termIdFiltersChange('$id', '$field_name');

    // simulate change so that the initial filter settings would be taken into account
    $('#$id-filters select.asmselect-filter:first').change();

  });";

  drupal_add_js($js, 'inline');
}

/**
 * Implementation of hook_widget_info().
 */
function nodereference_asmselect_widget_info() {
  return array(
    'nodereference_asm_select' => array(
      'label' => t('ASM Select List'),
      'field types' => array('nodereference'),
    )
  );
}

function _nodereference_asmselect_add_vocabulary_filters(&$node, $field) {
  $id = form_clean_id($field['field_name']);

  $taxonomy_field = array(
    '#type' => 'fieldset',
    '#title' => t('Filters'),
    '#collapsible' => TRUE,
    '#attributes' => array('id' => $id .'-filters'),
    '#collapsed' => FALSE    
  );

  $vocabs = taxonomy_get_vocabularies($node->type);

  $filters = array();

  foreach ($vocabs as $vocab) {
    // should I add a filter for this vocabulary?
    if (in_array($vocab->vid, $field['widget']['vocabulary_filters'])) {
      $terms = taxonomy_node_get_terms_by_vocabulary($node->nid, $vocab->vid);
      $filter = taxonomy_form($vocab->vid, $node->taxonomy);
      $filter['#id'] = $id .'-filter-vocab-'. $vocab->vid;
      $filter['#attributes']['class'] = 'asmselect-filter';
      $filter['#default_value'] = $terms ? array_keys($terms) : NULL;

      $filters[$vocab->name] = $filter;
    }
  }

  _nodereference_asmselect_add_filters_js($id, $field['field_name']);

  if (count($filters)) {
    return array_merge($taxonomy_field, $filters);
  }
}


/**
 * @param $field
 * @param $filters array of arrays ['type', 'value']
 */
function _nodereference_asmselect_get_filtered_options($field, $filters) {
  if (module_exists('views') && isset($field['advanced_view'])) {
    $view = NULL;

    if ($field['advanced_view'] != '--') {
      $view = views_get_view($field['advanced_view']);
    }

    // if view does not exist, create one on the fly
    if (!$view) {
      $view = views_create_view('candidate_nodes', '');

      foreach ($field['referenceable_types'] as $related_type) {
        if ($related_type) {
          views_view_add_filter($view, 'node', 'type', '=', $related_type, NULL);
        }
      }
    }

    // arguments for the view
    $view_args = array();
    if (isset($field['advanced_view_args'])) {
      // TODO: Support Tokens using token.module ?
      $view_args = array_map(trim, explode(',', $field['advanced_view_args']));
    }

    if (isset($filters) && is_array($filters) && count($filters) > 0) {
      foreach ($filters as $filter) {
        switch ($filter['type']) {
          case 'tid':
            views_view_add_filter($view, 'term_node', 'tid', '=', array($filter['value']), 20);
            break;

            //TODO: other types?
        }
      }
    }

    // we do need title field, so add it if not present (unlikely, but...)
    $has_title = array_reduce($view->field, create_function('$a, $b', 'return ($b["field"] == "title") || $a;'), FALSE);
    if (!$has_title) {
      views_view_add_field($view, 'node', 'title', '');
    }
    views_load_cache();
    views_sanitize_view($view);

    // make sure the fields get included in the query
    $view->page = TRUE;
    $view->page_type = 'list';

    // make sure the query is not cached
    unset($view->query); // Views 1.5-
    $view->is_cacheable = FALSE; // Views 1.6+

    $view_result = views_build_view('result', $view, $view_args);
    $result = $view_result['result'];
  }

  $rows = array();

  while ($node = db_fetch_object($result)) {
    if ($return_full_nodes) {
      $rows[$node->nid] = $node;
    }
    else {
      $rows[$node->nid] = $node->node_title;
    }
  }

  return $rows;
}


/**
 * Implementation of hook_widget().
 */
function nodereference_asmselect_widget($op, &$node, $field, &$items) {
  if ($field['widget']['type'] == 'nodereference_asm_select') {
    // call node reference module, pass a dummy field so that a regular select field is returned
    $nodereference_field = $field;
    $nodereference_field['widget']['type'] = 'nodereference_select';

    $ret = nodereference_widget($op, $node, $nodereference_field, $items);

    if ($op == 'form' && !isset($node->cck_dummy_node_form)) {
      // set new id
      $id = form_clean_id('edit-'. $field['field_name'] .'-nids');

      $ret[$field['field_name']]['nids']['#id'] = $id;

      // remove the <none> option
      $options = &$ret[$field['field_name']]['nids']['#options'];

      if (array_key_exists(0, $options)) {
        unset($options[0]);
      }
      
      $path = drupal_get_path('module', 'nodereference_asmselect');

      drupal_add_js($path .'/asmselect/jquery.asmselect.js');
      drupal_add_css($path .'/asmselect/jquery.asmselect.css');

      if ($field['widget']['sortable']) {
        // add list sorting
        jquery_ui_add(array('ui.draggable', 'ui.droppable', 'ui.sortable'));
      }

      // add sortable multiple select widget
      $js = "
        $(document).ready(function() {
          $('#". $id ."').asmSelect({
            addItemTarget: '". (isset($field['widget']['add_item_target']) ? $field['widget']['add_item_target'] : 'bottom') ."',
            sortable: ". (isset($field['widget']['sortable']) ? $field['widget']['sortable'] : 'false') .",
            animate: ". (isset($field['widget']['animate']) ? $field['widget']['animate'] : 'true') .",
            highlight: ". (isset($field['widget']['highlight']) ? $field['widget']['highlight'] : 'true') .",
            removeLabel: '". t('Remove') ."',
            highlightAddedLabel: '". t('Added ') ."',
            highlightRemovedLabel: '". t('Removed ') ."'
        });
      });";

      drupal_add_js($js, 'inline');

      // add vocabulary filters?
      if (isset($field['widget']['vocabulary_filters'])) {
        $ret[$field['field_name']]['filters'] = _nodereference_asmselect_add_vocabulary_filters($node, $field);
      }

      // add the "new node" buttons
      if ($field['widget']['create_new']) {
        // add "create new" button for each referencable type
        $create_new_fieldset = array(
          '#type' => 'fieldset',
          '#collapsible' => TRUE,
          '#collapsed' => FALSE    
        );

        foreach ($field['referenceable_types'] as $type => $is_referenceable) {
          if ($is_referenceable) {
            $create_new_fieldset[$type] = array(
              '#value' => theme('asm_select_create_new_button', $type, $id)
            );
          }
        }

        $ret[$field['field_name']]['create_new'] = $create_new_fieldset;
      }
    }
  }

  return $ret;
}


/**
 * Implementation of hook_widget_settings()
 */
function nodereference_asmselect_widget_settings($op, $widget) {
  switch ($op) {
    case 'form':
      $form = array();

      // add some settings for this field (these are all ASM Select settings)
      $form['asm_settings'] = array(
        '#type' => 'fieldset',
        '#title' => t('ASM Select Settings')
      );

      $form['asm_settings']['add_item_target'] = array(
        '#type' => 'select',
        '#title' => t('Add Item Target'),
        '#description' => t('Where to place new selected items that are added to the list.'),
        '#options' => array(
          'bottom' => t('Bottom'),
          'top' => t('Top'),
        ),      
        '#default_value' => isset($widget['add_item_target']) ? $widget['add_item_target'] : 'bottom',
        '#required' => TRUE,
      );

      $form['asm_settings']['sortable'] = array(
        '#type' => 'checkbox',
        '#title' => 'Sortable',
        '#description' => t('May the user drag and drop to sort the list of elements they have selected?'),
        '#default_value' => isset($widget['sortable']) ? $widget['sortable'] : false
      );
      
      $form['asm_settings']['animate'] = array(
        '#type' => 'checkbox',
        '#title' => 'Animate',
        '#description' => t('Animate the adding or removing of items from the list?'),
        '#default_value' => isset($widget['animate']) ? $widget['animate'] : true
      );      

      $form['asm_settings']['highlight'] = array(
        '#type' => 'checkbox',
        '#title' => 'Highlight',
        '#description' => t('Show a quick highlight of what item was added or removed?'),
        '#default_value' => isset($widget['highlight']) ? $widget['highlight'] : true
      );  

      $form['asm_settings']['create_new'] = array(
        '#type' => 'checkbox',
        '#title' => 'Allow Create',
        '#description' => t('Add a link to create a new node of the referenced type (will open in a new window)?'),
        '#default_value' => isset($widget['create_new']) ? $widget['create_new'] : false
      ); 

      $vocabularies = taxonomy_get_vocabularies();

      $options = array();

      foreach ($vocabularies as $vocabulary) {
        $options[$vocabulary->vid] = $vocabulary->name;
      }

      $form['asm_settings']['vocabulary_filters'] = array(
        '#type' => 'checkboxes',
        '#title' => t('Vocabulary Filters'),
        '#description' => t('Which vocabularies to use in order to filter this select?'),
        '#options' => $options,
        '#default_value' => isset($widget['vocabulary_filters']) ? $widget['vocabulary_filters'] : null
      );     
      
      return $form;

    case 'save':
      return array('add_item_target', 'sortable', 'animate', 'highlight', 'create_new', 'vocabulary_filters');

    case 'callbacks':
      return array(
        'default value' => CONTENT_CALLBACK_NONE,
      );
  }
}


/**
 * Implementation of hook_form_alter()
 */
function nodereference_asmselect_form_alter($form_id, &$form) {
  // should I work on this form?
  if (strpos($form_id, 'node_form') !== FALSE && isset($_GET['asmSelect'])) { 
    $_SESSION[ASM_SELECT_WIDGET_ID] = $_GET['asmSelect'];
    $form['#submit']['nodereference_asmselect_node_create_callback'] = array(); 
  }
}


/**
 * Called after a node form has been submitted to add JS that injects the node back
 * into our select widget
 */
function nodereference_asmselect_node_create_callback($form_id, $form_values) {
  $_SESSION[ASM_SELECT_NODE_TYPE] = $form_values['type'];
}


/**
 * Implementation of hook_nodeapi
 */
function nodereference_asmselect_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  // check if this is the first node view after user has created it from an ASM select
  // link
  if ($op == 'view' 
      && isset($_SESSION[ASM_SELECT_NODE_TYPE]) 
      && $node->type == $_SESSION[ASM_SELECT_NODE_TYPE]) {
    
    $select_id = $_SESSION[ASM_SELECT_WIDGET_ID];
    
    $title = drupal_urlencode($node->title);
    
    drupal_add_js("$(document).ready(function() {
      var asmSelect = window.opener.$('#$select_id');
      var select = asmSelect.get(0);
      
      var opt = document.createElement('option');
      opt.value = $node->nid;
      opt.selected = 'selected';
      opt.appendChild(document.createTextNode('$title'));   
      
      select.options[select.length] = opt;
      
      asmSelect.change();
      
      window.opener.focus();
      window.close();
    });", "inline");    
    
    // remove variable from session
    session_unregister(ASM_SELECT_NODE_TYPE);   
  }
}


/**
 * @param $type the node type to create a button for
 */
function theme_asm_select_create_new_button($type, $select_id) {
  $node_names = node_get_types('names');
  
  $path = url("node/add/$type") ."&asmSelect=". $select_id;
  
  return '<a href="#" onclick="window.open(\''. $path .'\'); return false">'. t('Create new !type', array('!type' => $node_names[$type])) .'</a>';
}

